package fabricsdk_go

import (
	"context"
	"fmt"
	"gitee.com/wayjin/fabricsdk-go/cryptoutil"
	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric/common/configtx"
	"github.com/hyperledger/fabric/common/util"
	"github.com/hyperledger/fabric/protos/common"
	cb "github.com/hyperledger/fabric/protos/common"
	"github.com/hyperledger/fabric/protos/peer"
	"github.com/hyperledger/fabric/protos/utils"
	"github.com/pkg/errors"
	"io/ioutil"
)

const (
	CSCC_GetChannels = "GetChannels"
	CSCC_JoinChain = "JoinChain"
	QSCC_GetChainInfo = "GetChainInfo"
)
type ChannelInfo struct {

}

// 查询已加入的channel
func listChannel(signer cryptoutil.Signer, endorser peer.EndorserClient) (*ChannelInfo, error) {
	invocation := &peer.ChaincodeInvocationSpec{
		ChaincodeSpec: &peer.ChaincodeSpec{
			Type: peer.ChaincodeSpec_Type(peer.ChaincodeSpec_Type_value["GOLANG"]),
			ChaincodeId: &peer.ChaincodeID{Name: "cscc"},
			Input:       &peer.ChaincodeInput{Args: [][]byte{[]byte(CSCC_GetChannels)}},
		},
	}
	var err error
	creator, err := signer.Serialize()
	if err != nil {
		return nil, err
	}

	var prop *peer.Proposal
	prop, _, err = utils.CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, "", invocation, creator)
	if err != nil {
		return nil, err
	}

	var signedProp *peer.SignedProposal
	signedProp, err = cryptoutil.GetSignedProposal(prop, signer)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Cannot create signed proposal, due to %s", err))
	}

	resp, err := endorser.ProcessProposal(context.Background(), signedProp)
	if err != nil {
		return nil, err
	}

	if resp.Response == nil || resp.Response.Status != 200 {
		return nil, errors.New(fmt.Sprintf("Received bad response, status %d: %s", resp.Response.Status, resp.Response.Message))
	}

	var channelQueryResponse peer.ChannelQueryResponse
	err = proto.Unmarshal(resp.Response.Payload, &channelQueryResponse)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Cannot read channels list response, %s", err))
	}

	fmt.Println("response: ", channelQueryResponse.Channels)

	return nil, nil
}

// 创建通道
func createChannel(signer cryptoutil.Signer, ordererCli *ordererClient, channelTxFile, channelID string) error {
	channelData, err := ioutil.ReadFile(channelTxFile)
	if err != nil {
		return err
	}
	chnEnv, err := utils.UnmarshalEnvelope(channelData)
	if err != nil {
		return err
	}

	signedChnEnv, err := sanityCheckAndSignConfigTx(chnEnv, signer, channelID)
	if err != nil {
		return err
	}

	broadcast, err := ordererCli.GetBroadcastClient()
	if err != nil {
		return errors.WithMessage(err, "error getting broadcast client")
	}

	defer broadcast.Close()

	err = broadcast.Send(signedChnEnv)
	return err
}

// 合理检查并签名channel交易包
func sanityCheckAndSignConfigTx(envConfigUpdate *cb.Envelope, signer cryptoutil.Signer, channelID string) (*cb.Envelope, error) {
	payload, err := utils.ExtractPayload(envConfigUpdate)
	if err != nil {
		return nil, errors.New("bad payload")
	}

	if payload.Header == nil || payload.Header.ChannelHeader == nil {
		return nil, errors.New("bad header")
	}

	ch, err := utils.UnmarshalChannelHeader(payload.Header.ChannelHeader)
	if err != nil {
		return nil, errors.New("could not unmarshall channel header")
	}

	if ch.Type != int32(cb.HeaderType_CONFIG_UPDATE) {
		return nil, errors.New("bad type")
	}

	if ch.ChannelId == "" {
		return nil, errors.New("empty channel id")
	}
	// Specifying the chainID on the CLI is usually redundant, as a hack, set it
	// here if it has not been set explicitly
	if channelID == "" {
		channelID = ch.ChannelId
	}

	if ch.ChannelId != channelID {
		return nil, errors.New(fmt.Sprintf("mismatched channel ID %s != %s", ch.ChannelId, channelID))
	}
	configUpdateEnv, err := configtx.UnmarshalConfigUpdateEnvelope(payload.Data)
	if err != nil {
		return nil, errors.New("Bad config update env")
	}

	sigHeader, err := signer.NewSignatureHeader()
	if err != nil {
		return nil, err
	}
	configSig := &cb.ConfigSignature{
		SignatureHeader: utils.MarshalOrPanic(sigHeader),
	}

	configSig.Signature, err = signer.Sign(util.ConcatenateBytes(configSig.SignatureHeader, configUpdateEnv.ConfigUpdate))

	configUpdateEnv.Signatures = append(configUpdateEnv.Signatures, configSig)

	return utils.CreateSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, channelID, signer, configUpdateEnv, 0, 0)
}

// 更新通道
func updateChannel(signer cryptoutil.Signer, ordererCli *ordererClient, updateEnv *common.Envelope, channelID string) error {
	signedEnv, err := sanityCheckAndSignConfigTx(updateEnv, signer, channelID)
	if err != nil {
		return err
	}

	broadcast, err := ordererCli.GetBroadcastClient()
	if err != nil {
		return err
	}

	defer broadcast.Close()
	err = broadcast.Send(signedEnv)
	if err != nil {
		return err
	}

	return nil
}

// 加入通道
func joinChannel(signer cryptoutil.Signer, endorser peer.EndorserClient, channelID string) error {
	gb := []byte("")
	// Build the spec
	input := &peer.ChaincodeInput{Args: [][]byte{[]byte(CSCC_JoinChain), gb}}
	spec := &peer.ChaincodeSpec{
		Type:        peer.ChaincodeSpec_Type(peer.ChaincodeSpec_Type_value["GOLANG"]),
		ChaincodeId: &peer.ChaincodeID{Name: "cscc"},
		Input:       input,
	}
	// Build the ChaincodeInvocationSpec message
	invocation := &peer.ChaincodeInvocationSpec{ChaincodeSpec: spec}

	creator, err := signer.Serialize()
	if err != nil {
		return fmt.Errorf("Error serializing identity for: %s",  err)
	}

	var prop *peer.Proposal
	prop, _, err = utils.CreateProposalFromCIS(common.HeaderType_CONFIG, "", invocation, creator)
	if err != nil {
		return fmt.Errorf("Error creating proposal for join %s", err)
	}

	var signedProp *peer.SignedProposal
	signedProp, err = cryptoutil.GetSignedProposal(prop, signer)
	if err != nil {
		return fmt.Errorf("Error creating signed proposal %s", err)
	}

	resp, err := endorser.ProcessProposal(context.Background(), signedProp)
	if err != nil {
		return fmt.Errorf("Proposal submit fail: %v", err)
	}

	if resp == nil {
		return errors.New("nil proposal response")
	}

	if resp.Response.Status != 0 && resp.Response.Status != 200 {
		return errors.New(fmt.Sprintf("bad proposal response %d: %s", resp.Response.Status, resp.Response.Message))
	}

	return nil
}
// 获取通道信息
func getChannelInfo(signer cryptoutil.Signer, endorser peer.EndorserClient, channelID string) (*cb.BlockchainInfo, error) {
	invocation := &peer.ChaincodeInvocationSpec{
		ChaincodeSpec: &peer.ChaincodeSpec{
			Type:        peer.ChaincodeSpec_Type(peer.ChaincodeSpec_Type_value["GOLANG"]),
			ChaincodeId: &peer.ChaincodeID{Name: "qscc"},
			Input:       &peer.ChaincodeInput{Args: [][]byte{[]byte(QSCC_GetChainInfo), []byte(channelID)}},
		},
	}
	var prop *peer.Proposal
	c, _ := signer.Serialize()
	prop, _, err := utils.CreateProposalFromCIS(cb.HeaderType_ENDORSER_TRANSACTION, "", invocation, c)
	if err != nil {
		return nil, errors.WithMessage(err, "cannot create proposal")
	}
	var signedProp *peer.SignedProposal
	signedProp, err = cryptoutil.GetSignedProposal(prop, signer)
	if err != nil {
		return nil, errors.WithMessage(err, "cannot create signed proposal")
	}

	resp, err := endorser.ProcessProposal(context.Background(), signedProp)
	if err != nil {
		return nil, errors.WithMessage(err, "failed sending proposal")
	}
	if resp.Response == nil || resp.Response.Status != 200 {
		return nil, errors.Errorf("received bad response, status %d: %s", resp.Response.Status, resp.Response.Message)
	}

	blockChainInfo := &cb.BlockchainInfo{}
	err = proto.Unmarshal(resp.Response.Payload, blockChainInfo)
	if err != nil {
		return nil, errors.Wrap(err, "cannot read qscc response")
	}

	return blockChainInfo, nil
}

// 获取通道区块
func fetchBlock(signer cryptoutil.Signer, peerCli *peerClient, channelID string, blockNum int64) (*common.Block, error) {
	dc, err := peerCli.GetDeliverClient(signer, channelID, true)
	if err != nil {
		return nil, err
	}

	switch blockNum {
	case 0:
		return dc.GetOldestBlock()
	case -1:
		return dc.GetNewestBlock()
	default:
		return dc.GetSpecifiedBlock(uint64(blockNum))
	}
}

// 获取通道配置
func fetchConfig(signer cryptoutil.Signer, peerCli *peerClient, channelID string) (*common.Block, error) {
	dc, err := peerCli.GetDeliverClient(signer, channelID, true)
	if err != nil {
		return nil, errors.WithMessage(err, "GetDeliverClient")
	}

	newestBlock, err := dc.GetNewestBlock()
	if err != nil {
		return nil, err
	}

	n, err := utils.GetLastConfigIndexFromBlock(newestBlock)
	if err != nil {
		return nil, errors.WithMessage(err, "GetLastConfigIndexFromBlock")
	}
	return dc.GetSpecifiedBlock(n)
}